package cn.turboinfo.fuyang.api.provider.common.util;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类
 *
 * @author gadzs
 */
@Component
@SuppressWarnings("unchecked")
public class RedisUtils {

    RedisTemplate redisTemplate;

    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setEnableDefaultSerializer(true);
        redisTemplate.afterPropertiesSet();
        this.redisTemplate = redisTemplate;
    }

    public Long getExpire(String key) {
        Long result = null;
        try {
            result = redisTemplate.getExpire(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 生成id
     */
    public String generatorId(String prefixKey, String idPrefix) {
        Date dt = new Date();
        DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
        String tm = df.format(dt);
        ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
        valueOper.increment(prefixKey, 1);
        long temp = getIncrValue(prefixKey);
        int random = (int) (Math.random() * 9 + 1);
        int tail = (int) (temp % 1000);

        int tail_000 = tail / 100;
        int tail_00 = (tail % 100) / 10;
        int tail_0 = tail % 10;
        int str = tail_000 + random + tail_00 + tail_0;
        return idPrefix + tm + str;
    }

    /**
     * 获取getIncrValue
     *
     * @param key
     * @return
     */
    public Long getIncrValue(final String key) {
        return (Long) redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] rowkey = serializer.serialize(key);
                byte[] rowval = connection.get(rowkey);
                try {
                    String val = serializer.deserialize(rowval);
                    return Long.parseLong(val);
                } catch (Exception e) {
                    return 0L;
                }
            }
        });
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存设置时效时间
     *
     * @param key
     * @param value
     * @param expireTime 有效时间（秒）
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 批量删除对应的value
     *
     * @param keys
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 批量删除key
     *
     * @param pattern
     */
    public void removePattern(final String pattern) {
        Set<Serializable> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 哈希 添加
     *
     * @param key
     */
    public void hmSet(String key, Map map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 哈希 获取
     *
     * @param key
     */
    public Map hmGet(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 哈希 添加
     *
     * @param key
     * @param hashKey
     * @param value
     */
    public void hmSet(String key, Object hashKey, Object value) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.put(key, hashKey, value);
    }

    /**
     * 哈希获取数据
     *
     * @param key
     * @param hashKey
     * @return
     */
    public Object hmGet(String key, Object hashKey) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.get(key, hashKey);
    }

    /**
     * 哈希获取批量数据
     *
     * @param key
     * @param hashKeys
     * @return
     */
    public List<Object> hmultlGet(String key, List<String> hashKeys) {
        return redisTemplate.opsForHash().multiGet(key, hashKeys);
    }

    /**
     * 哈希获取数据
     *
     * @param key
     * @param hashKey
     * @return
     */
    public Boolean hmExist(String key, Object hashKey) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.hasKey(key, hashKey);
    }

    /**
     * 哈希删除数据
     *
     * @param key
     * @param hashKey
     * @return
     */
    public Long hmDel(String key, Object hashKey) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.delete(key, hashKey);
    }

    /**
     * 在名称为key的list头添加一个值为value的 元素
     *
     * @param key   list名称
     * @param value 添加的元素
     */
    public void leftPushAll(String key, List<Object> value) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.leftPushAll(key, value);
    }

    /**
     * 在名称为key的list头添加一个值为value的 元素
     *
     * @param key   list名称
     * @param value 添加的元素
     */
    public void leftPushAllMap(String key, List<Map> value) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.leftPushAll(key, value);
    }

    /**
     * 在名称为key的list头添加一个值为value的 元素
     *
     * @param key   list名称
     * @param value 添加的元素
     */
    public void leftPush(String key, Object value) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.leftPush(key, value);
    }

    /**
     * 在名称为key的list头添加一个值为value的 元素
     *
     * @param key   list名称
     * @param value 添加的元素
     */
    public void rightPush(String key, Object value) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPush(key, value);
    }

    /**
     * 在名称为key的list头添加一个值为value的 元素
     *
     * @param key   list名称
     * @param value 添加的元素
     */
    public void rightPushAll(String key, List<Object> value) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPushAll(key, value);
    }

    /**
     * 在名称为key的list头添加一个值为value的 元素
     *
     * @param key   list名称
     * @param value 添加的元素
     */
    public void rightPushAllMap(String key, List<Map> value) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPushAll(key, value);
    }

    /**
     * 删除并返回存储在key的列表中的最后一个元素。
     *
     * @param key list名称
     * @return
     */
    public Object brpop(String key) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.rightPop(key);
    }

    /**
     * 删除，并获得该列表中的最后一个元素，或阻塞，直到有一个可用
     *
     * @param key     list名称
     * @param timeout 允许超时时间
     * @param unit    时间类型
     * @return
     */
    public Object brpop(String key, long timeout, TimeUnit unit) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.rightPop(key, timeout, unit);
    }

    /**
     * 获取List长度
     *
     * @param key list名称
     * @return
     */
    public Long llen(String key) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.size(key);
    }

    /**
     * 集合添加
     *
     * @param key
     * @param value
     */
    public void add(String key, Object value) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key, value);
    }

    /**
     * 集合获取
     *
     * @param key
     * @return
     */
    public Set<Object> setMembers(String key) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * 有序集合添加
     *
     * @param key
     * @param value
     * @param scoure
     */
    public void zAdd(String key, Object value, double scoure) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add(key, value, scoure);
    }

    /**
     * 有序集合获取
     *
     * @param key
     * @param scoure
     * @param scoure1
     * @return
     */
    public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rangeByScore(key, scoure, scoure1);
    }

    /**
     * 列表获取
     *
     * @param k
     * @param l
     * @param l1
     * @return
     */
    public List<Object> lRange(String k, long l, long l1) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.range(k, l, l1);
    }

    /**
     * 批量执行set (统一连接下执行多个命令)
     *
     * @param map
     */
    public void batchSet(Map<String, Object> map) {
        redisTemplate.executePipelined(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                map.forEach((key, value) -> {
                    operations.opsForValue().set(key, value);
                });
                return null;
            }
        });
    }

    /**
     * 批量执行get
     *
     * @param keys
     */
    public Map<String, Object> batchGet(List<String> keys) {
        Map<String, Object> mapResult = new HashMap<>();
        List<Object> values = batchGetValue(keys);
        for (int i = 0; i < values.size(); i++) {
            mapResult.put(keys.get(i), values.get(i));
        }
        return mapResult;
    }

    /**
     * 批量执行get (piepline)
     *
     * @param keys
     */
    public List<Object> batchGetValue(List<String> keys) {
        List<Object> values = (List<Object>) redisTemplate.executePipelined(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                keys.forEach((key) -> {
                    operations.opsForValue().get(key);
                });
                return null;
            }
        });
        return values;
    }


    /**
     * 执行事务
     *
     * @param key
     */
    public void execTransaction(String key) {
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.watch(key);
                operations.multi();//开始事务
                //执行事务内容
                //先判断key是否在监控后被修改过，如果被修改就不执行事务内容
                return operations.exec();
            }
        });
    }

    /**
     * 存储地址位置
     *
     * @param geoKey
     * @param members
     */
    public void geoAdd(String geoKey, Map<String, Point> members) {
        redisTemplate.opsForGeo().add(geoKey, members);
    }

    /**
     * 获取地址位置
     *
     * @param geoKey
     * @param memberKeys
     * @return
     */
    public List<Point> getGeoPoints(String geoKey, Object... memberKeys) {
        return redisTemplate.opsForGeo().position(geoKey, memberKeys);
    }

    /**
     * 获取地址位置
     *
     * @param geoKey
     * @param memberKey
     * @return
     */
    public Point getGeoPoint(String geoKey, Object memberKey) {
        List<Point> points = getGeoPoints(geoKey, memberKey);
        if (points != null && points.size() > 0) {
            return points.get(0);
        }
        return null;
    }

    /**
     * 方位两个地址的距离，默认单位，千米km
     *
     * @param geoKey
     * @param memberKey1
     * @param memberKey2
     * @return
     */
    public Distance getGeoDist(String geoKey, String memberKey1, String memberKey2) {
        return getGeoDist(geoKey, memberKey1, memberKey2, Metrics.KILOMETERS);
    }

    /**
     * 方位两个地址的距离，可以指定单位，比如米m，千米km，英里mi，英尺ft
     *
     * @param geoKey
     * @param memberKey1
     * @param memberKey2
     * @param metric
     * @return
     */
    public Distance getGeoDist(String geoKey, String memberKey1, String memberKey2, Metric metric) {
        return redisTemplate.opsForGeo().distance(geoKey, memberKey1, memberKey2, metric);
    }

    /**
     * 根据给定的经纬度，返回半径不超过指定距离的元素
     * redis命令：georadius key 116.405285 39.904989 100 km WITHDIST WITHCOORD ASC
     * count 返回数量
     */
    public GeoResults<RedisGeoCommands.GeoLocation<Object>> getGeoNearBy(String geoKey, Circle circle, long count) {
        // includeDistance 包含距离
        // includeCoordinates 包含经纬度
        // sortAscending 正序排序
        // limit 限定返回的记录数
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeDistance().includeCoordinates().sortAscending().limit(count);
        return redisTemplate.opsForGeo().radius(geoKey, circle, args);
    }

    /**
     * 根据指定的地点查询半径在指定范围内的位置
     * redis命令：georadiusbymember key 北京 100 km WITHDIST WITHCOORD ASC COUNT 5
     */
    public GeoResults<RedisGeoCommands.GeoLocation<Object>> getGeoNearBy(String geoKey, String member, Distance distance,
                                                                         long count) {
        // includeDistance 包含距离
        // includeCoordinates 包含经纬度
        // sortAscending 正序排序
        // limit 限定返回的记录数
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeDistance().includeCoordinates().sortAscending().limit(count);
        return redisTemplate.opsForGeo().radius(geoKey, member, distance, args);
    }

    /**
     * 返回的是geohash值
     * redis命令：geohash key 北京
     */
    public List<String> getGeoHash(String geoKey, Object... memberKey) {
        return redisTemplate.opsForGeo().hash(geoKey, memberKey);
    }
}
